/**
 * 基础工具模块
 */

"use strict";

var http      = require('http');
var https     = require('https');
var sysUtil   = require('util');
var radon     = require('radon');
var fs		  = require('fs');
var util      = radon.util;

/*
 数据验证方法
 1. 必填验证 (necessary||true)
 2. 值验证
 按照类型验证 (type)
 限定值验证 (enum: [])
 自定义验证 (check: function)
 *. 错误代码 (code||15002/15003); 错误信息 (message)

 支持多种写法，最大限度减少业务代码量
 1. validate(data, name, type) e.g include.validate(data, 'id', Number);
 2. validate(data, name, option) e.g include.validate(data, 'id', {type: Number});
 3. validate(data, name, check ) e.g include.validate(data, 'id', function(){});
 4. validate(data, field)
 e.g include.validate(data, {
 'ids': String,
 'oldValue': String,
 'newValue': function(){}
 'other': {type: String, check: fucntion(){}, necessary: false}
 });
 */
exports.validate = function (data, field, type) {
	if ( !data ) {
		radon.throw(10000601);
	}
	if ( !field ) {
		return; //无需判断
	}

	//field为字符串
	if ( 'string' == typeof field ) {
		_validate(field, data[field], type);
		return;
	}
	//field为对象
	for ( var f in field ) {
		_validate(f, data[f], field[f]);
	}
	return;

	//验证入口
	var _value;
	function _validate( field, value, option ) {
		//opt解析封装
		var defaultOpt = {
			necessary: true,
			type: 'Object'
		}
		var opt = option || defaultOpt;
		if ('function' === typeof option) { //option是类型或者函数
			opt = {};
			if ( ['String', 'Number', 'Boolean', 'Object', 'Array'].indexOf(option.name) !== -1) {
				opt.type = option.name;
			} else {
				opt.check = option;
			}
			opt.type = opt.type || defaultOpt.type;
			opt.necessary = defaultOpt.necessary;
		} else { //option是对象
			if ( 'function' === typeof opt.type ) {
				opt.type = opt.type.name; //type转化下
			}
			if ( !opt.hasOwnProperty('necessary') ){
				opt.necessary = defaultOpt.necessary;
			}
		}

		var err, code, message;
		//空值检查
		if (!Object.prototype.hasOwnProperty.call(data, field)) {
			if (opt['necessary']) {//必填提示
				err = {code: 10000602, param: [field]};
				_throwErr(err, opt);
			}
			return;
		}
		//类型空值检查
		err = _check_type_empty(field, value, opt['type']);
		if (err) {
			if (opt['necessary']) {//必填提示
				_throwErr(err, opt);
			}
			return;
		}

		//todo 需要考虑下这个函数的效率
		//类型转换下
		value = _get_type_value(value, opt['type']);

		//enum 限定值检查
		if (opt['enum'] && opt['enum'].length) {
			if (opt['enum'].indexOf(value) === -1) {
				err = {code: 10000604, param: [field]};
				_throwErr(err, opt);
			}
		}

		//自定义检查和处理
		if ( opt['check'] && typeof opt['check']==='function' ) {
			opt['check'](value);
		}
	}
	//检查类型值为空
	function _check_type_empty(field, value, type) {
		var err, tip;
		if ( value === undefined ) {
			//必填提示
			return {code: 10000603, param: [field]};
		}
		switch (type) {
			case 'String':
				err = !value || ("" + value) === "";
				tip = util.LANG('可以转化为字符串');
				break;
			case 'Array':
				err = !Array.isArray(value) || !value.length;
				tip = util.LANG('长度不为0的数组');
				break;
			case 'Number':
				err = value === null || isNaN(+value); //null不通过, 容易误解
				tip = util.LANG('可以转化为数字');
				break;
		}
		//必填提示
		return err && {code: 10000605, param: [field, tip]};
	}
	//数据转换
	function _get_type_value(value, type) {
		switch (type) {
			case 'String':
				return "" + value;
				break;
			case 'Number':
				return +value;
				break;
			case 'Boolean':
				return !!value;
				break;
		}
		return value;
	}
	//抛出错误
	function _throwErr(err, opt) {
		var code = opt['code'] || err.code;
		var message = opt['message'];
		var param = err.param;
		radon.throw(code, message || param, param);
	}
};

/**
 * 按url得到文件
 * @param url
 * @param dest
 * @returns {*} writeStream
 */
function getUrlFile(url, dest) {
	if(!url) {
		return Promise.reject({message: util.LANG('没有Url地址')});
	}

	var file = fs.createWriteStream(dest);
	return util.promise(function(ok, fail) {
		var req = http.get(url, function(response) {
			response.pipe(file);
			file.on('finish', function() {
				file.close();  // close() is async, call cb after close completes.
				ok(file)
			});
		});

		req.on('error', function(e) {
			fs.unlink(dest);
			fail({code:509,message: radon.util.LANG('远程计算机拒绝连接')});
		});
	});
};
exports.getUrlFile = getUrlFile;

function parseURI(remote,failCallback) {
	var options = {'http': http};
	try {
		/*
		 0 url: http://www.ora.com:80/goodparts?q#fragment
		 1 scheme: http
		 2 slash: //
		 3 host: www.ora.com
		 4 port: 80
		 5 path: goodparts
		 6 query: q
		 7 hash: fragment
		 */
		var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
		var ret = parse_url.exec(remote);
		options['host'] = ret[3];
		options['port'] = ret[4];

		let path = ret[5] && ret[5].trim();
		options['path'] =  (path || '/') + (ret[6] ? `?${ret[6]}` : '');

		options['query'] = ret[6] ||'';
		if(ret[1] === 'https') {
			options['http'] = https;
		}
		if (options['path'].indexOf('/') !== 0) {
			options['path'] = '/' + options['path'];
		}
	}
	catch(e) {
		util.log(59, '请求地址的url错误,地址为：%s', remote);
		if(failCallback){
			failCallback(e);
		}
		return;
	}
	return options;
}

exports.parseURI = parseURI;


function http_get(url, params, type, timeout) {
	if(!url) {
		return Promise.reject({message: util.LANG('没有Url地址')});
	}
	return util.promise(function(ok, fail) {
		var result = null;
		var body = '';
		if(typeof params === 'string'){
			type = params;
			params = null;
		}

		type = type || 'json';
		for(var i in params){
			if(params.hasOwnProperty(i)) {
				if(body){
					body += '&';
				}

				body += i + '=' + params[i];
			}

		}

		if(body && url.indexOf('?') > -1) {
			body = '&' + body;
		}
		else {
			body = '?' + body;
		}
		
		var getUrl = encodeURI(url + body);
		var Http = http;
		if(~url.indexOf('https')) {
			Http = https;
		}

		var hasTimeouted;
		var req = Http.get(getUrl, function(res) {
			var ret = [];
			res.on('data', function(body) {
				try
				{
					if (body)
					{
						ret.push(body);
					}
				}
				catch(e)
				{
					return fail({message: 'On Data Error', code: e.code});
				}
			});

			res.on('end', function() {
				try
				{
					if(hasTimeouted)
					{
						ret = null;
						return;
					}
					if(ret && ret.length)
					{
						switch(type)
						{
							case 'json':
								result = JSON.parse(ret.join(''));
								break;
							case 'buffer':
								result = new Buffer(Buffer.concat(ret));
								break;
						}

						ret = null;
						ok(result);
					}
					else{
						ret = null;
						ok(null);
					}
				}
				catch(e)
				{
					ret = null;
					fail({message: 'Data Error', code: e.code});
				}
			});
		});

		req.on('error', function(e) {
			util.log(104, `The Url Get Error ${getUrl}`);
			fail({code:509,message: radon.util.LANG('远程计算机拒绝连接: ' + getUrl)});
		});

		timeout = timeout || radon.config('app.http_timeout') || 300000;
		if(timeout)
		{
			req.setTimeout(timeout, function() {
				util.log(104, `the http get timeout(${timeout/1000} sec) url: ${getUrl}`);
				hasTimeouted = true;
				req.abort();
				fail({code:509,message: radon.util.LANG('远程计算机超时: ' + getUrl)});
			});
		}
	});
}
exports.http_get = http_get;

function http_post(remote, params, bodyType, auth, timeout, backType) {
	if(timeout == 'buffer') {
		backType = timeout;
		timeout = undefined;
	}

	return util.promise(function(ok, fail) {
		var path, port, host,query;
		var Http = http;
		if(sysUtil.isString(remote)) {
			var options = parseURI(remote);
			host = options.host;
			Http = options.http;
			port = options.port;
			path = options.path;
			query = options.query;
		}
		else {
			host = remote.host;
			port = remote.port;
			path = remote.path;
			if(remote.isHttps) {
				Http = https;
			}
		}

		var body = '';
		var contentType = '';
		if(bodyType === 'json'){
			contentType = 'application/json';
			body = JSON.stringify(params);
		}else{
			for(var i in params){
				if(params.hasOwnProperty(i)) {
					if(body){
						body += '&';
					}

					let s = params[i];
					if(params[i] instanceof Object) {
						s = JSON.stringify(s);
					}

					body += i + '=' + s;
				}
			}
			contentType = 'application/x-www-form-urlencoded';
		}

		if(path.indexOf('/') !== 0) {
			path = '/' + path;
		}
		var options = {
			hostname: host
			,path: path
			,query:query
			,headers: {
				"Content-Type"	: contentType
				,"Content-Length": Buffer.byteLength(body)
				,"Accept": 'application/json'
			}
			,method: 'POST'
		};

		if(auth) {
			options.headers.Authorization = auth;
		}

		if(params.cookie){
			options.headers.Cookie = params.cookie;
		}

		if(port) {
			options.port = port;
		}

		var hasTimeouted;
		var req = Http.request(options, function(res) {
			var ret = [];
			res.on('data', function(body) {
				try
				{
					ret.push(body);
				}
				catch(e)
				{
					return fail({message: 'On Data Error', code: e && e.code});
				}
			});

			res.on('end', function() {
				try
				{
					if(hasTimeouted)
					{
						return;
					}
					if(ret)
					{
						if(backType && backType == 'buffer') {
							ok(Buffer.concat(ret));
						}
						else {
							ok(JSON.parse(ret.join('')));
						}
					}
					else
					{
						ok(null);
					}

				}catch (e)
				{
					fail({message: 'Data Parese Error', code: e.code, result: ret && ret.join && ret.join('')});
				}
			});
		});

		req.on('error', function(e) {
			util.log(104, `The Url Post Error ${remote}`);
			fail({code:509,message: radon.util.LANG('远程计算机拒绝连接: ' + remote)});
		});

		req.write(body);
		req.end();

		timeout = timeout || radon.config('app.http_timeout') || 300000;
		if(timeout)
		{
			req.setTimeout(timeout, function() {
				util.log(104, `the http post timeout(${timeout/1000} sec) url: ${remote}`);
				hasTimeouted = true;
				req.abort();
				fail({code:509,message: radon.util.LANG('远程计算机超时: ' + remote)});
			});
		}
	});
}
exports.http_post = http_post;

exports.http_request = function* (param) {
	return util.promise(function(ok, fail)
	{
		var path, port, host,query;
		var Http = http;
		var remote = param.remote;
		var headers = param.headers;
		var method = param.method || 'GET';
		var bodyType = param.bodyType || 'json';
		var params = param.param;
		var auth = param.auth;
		var timeout = param.timeout || 0;
		if(sysUtil.isString(remote)) {
			var options = parseURI(remote);
			host = options.host;
			port = options.port;
			path = options.path;
			query = options.query;
			Http = options.http;
		}
		else {
			host = remote.host;
			port = remote.port;
			path = remote.path;
			if(remote.isHttps) {
				Http = https;
			}
		}
		
		if(path.indexOf('/') !== 0)
		{
			path = '/' + path;
		}

		var options = {
			hostname: host
			,path: path
			,method: method
		};

		if(params)
		{
			var body = '';
			var contentType = '';
			if(bodyType === 'json'){
				contentType = 'application/json';
				body = JSON.stringify(params);
			}else{
				for(var i in params){
					if(params.hasOwnProperty(i)) {
						if(body){
							body += '&';
						}
						body += i + '=' + params[i];
					}
				}

				contentType = 'application/x-www-form-urlencoded';
			}

			options.headers = {
				"Content-Type"	: contentType
				,"Content-Length": Buffer.byteLength(body)
				,"Accept": 'application/json'
			};
		}

		if(headers) {
			for(var k in headers){
				if(headers.hasOwnProperty(k)){
					options.headers[k] = headers[k];
				}
			}
		}


		if(auth)
		{
			options.headers.Authorization = auth;
		}

		if(port) {
			options.port = port;
		}
		if(param.hasOwnProperty('rejectUnauthorized'))
		{
			options.rejectUnauthorized = param.rejectUnauthorized;
		}

		var hasTimeouted;
		var req = Http.request(options, function(res) {
			var ret = [];
			res.on('data', function(body) {
				try
				{
					if(body)
					{
						ret.push(body);
					}
				}
				catch(e)
				{
					return fail({message: 'On Data Error', code: e && e.code});
				}
			});

			res.on('end', function() {
				var result;
				try
				{
					if(hasTimeouted)
					{
						return;
					}
					if(ret && ret.length)
					{
						switch(bodyType)
						{
							case 'json':
								result = JSON.parse(ret.join(''));
								break;
							case 'buffer':
								result = new Buffer(Buffer.concat(ret));
								break;
						}

						ret = null;
						ok(result);
					}
					else{
						ret = null;
						ok(null);
					}
				}
				catch (e)
				{
					util.log(104, `Http Request Url is ${JSON.stringify(options)}; And the return result is: ${ret && ret.join('')}`);
					fail({message: 'Data Parese Error', code: e.code});
				}
			});
		});

		req.on('error', function(e) {
			util.log(104, `The Url Request Error ${remote}`);
			fail({code:509,message: radon.util.LANG('远程计算机拒绝连接: ' + remote)});
		});

		if(body)
		{
			req.write(body);
		}

		req.end();

		timeout = timeout || radon.config('app.http_timeout') || 300000;
		if(timeout)
		{
			req.setTimeout(timeout, function() {
				util.log(104, `the http request timeout(${timeout/1000} sec) url: ${remote}`);
				hasTimeouted = true;
				req.abort();
				fail({code:509,message: radon.util.LANG('远程计算机超时: ' + remote)});
			});
		}
	});
};


let MIME = {
	"*":"application/octet-stream",
	"css": "text/css",
	"gif": "image/gif",
	"html": "text/html",
	"ico": "image/x-icon",
	"jpeg": "image/jpeg",
	"jpg": "image/jpeg",
	"js": "text/javascript",
	"json": "application/json",
	"pdf": "application/pdf",
	"png": "image/png",
	"svg": "image/svg+xml",
	"swf": "application/x-shockwave-flash",
	"tiff": "image/tiff",
	"txt": "text/plain",
	"wav": "audio/x-wav",
	"wma": "audio/x-ms-wma",
	"wmv": "video/x-ms-wmv",
	"flv": "video/x-flv",
	"avi": "video/x-msvideo",
	"mp4": "video/mp4",
	"mov": "video/quicktime",
	"xml": "text/xml",
	"md": "test/markdown",
	'7z': 'application/x-7z-compressed',
	'rar': 'application/x-rar-compressed',
	'zip': 'application/zip',
	'doc': 'application/msword',
	'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
	'ppt': 'application/vnd.ms-powerpoint',
	'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
	'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
	'xls':'application/vnd.ms-excel'
};

exports.MIME = MIME;
/**
 *
 * @param requestUrl
 * @param filePaths filePaths 接受形如：{path:"adfdf.jpg",name:"image"}的参数
 * @param params
 * @returns {*}
 */
function http_post_form(requestUrl,filePaths, params) {

	return util.promise(function (ok, fail) {

		let options = parseURI(requestUrl);

		options['filePaths'] = [];
		filePaths = !Array.isArray(filePaths) ? [filePaths] :filePaths;

		let pathObj;
		for(var i = 0;i <filePaths.length;i++){
			pathObj = filePaths[i];

			if(typeof(pathObj) == 'string'){
				options['filePaths'].push({path:pathObj,name:"file"+ i});
			} else {
				if(!pathObj.name || !pathObj.path){
					fail({message: 'param:filePaths is like:[{path:"adfdf.jpg",name:"image"}]'});
					return;
				}
				options['filePaths'].push(pathObj);
			}
		}

		let boundaryKey = "hawk" + util.md5(Math.random().toString(32));
		let startData = [], fileUrl = [];
		let contentLength = 0;
		let endData = `\r\n--${boundaryKey}--\r\n`;//http body结束

		let paramsData = [],dataStr;
		for (var key in params || {}) {
			dataStr = '--' + boundaryKey + '\r\n'
				+ 'Content-Disposition: form-data; name="' + key + '"\r\n\r\n'
				+ JSON.stringify(params[key]);

			paramsData.push(dataStr);
		}

		let fileIndex = 0;
		let file, ext, filename, stat,fileTotalSize = 0,mime;
		for (var i = 0; i < options['filePaths'].length; i++) {

			file = options['filePaths'][i].path;
			filename = file.replace(/\\/g, "\/").split('/').pop();

			if(filename.indexOf('.') > -1){
				ext = filename.split(".").pop().toLowerCase();
			} else {
				ext = "*";
			}

			if(fs.existsSync(file)){
				stat = fs.statSync(file);
				fileUrl[fileIndex] = file;
				mime = MIME[ext] || MIME['*'];
				startData[fileIndex] = '--' + boundaryKey + '\r\n'
					// Content-Disposition必须放在第一个，关联到必要参数的初始化
					+ `Content-Disposition: form-data; name="${options['filePaths'][i].name}"; filename="${filename}"\r\n`
					+ 'Content-Type: ' + mime + '\r\n'
					+ 'Content-Transfer-Encoding: binary\r\n\r\n';

				fileTotalSize += stat.size;
				fileIndex++;
			}
		}

		if(paramsData.length){
			paramsData = paramsData.join('\r\n');
			contentLength += Buffer.byteLength(paramsData);//参数占报文长度
		}

		if(fileIndex > 0){
			//有文件传输
			contentLength += Buffer.byteLength(startData.join('\r\n'));//文件
			contentLength += fileTotalSize;//文件占报文长度
		}

		if(paramsData && fileIndex){
			//文件与参数列之间的分隔
			contentLength += Buffer.byteLength('\r\n');//分隔符占报文长度
		}

		contentLength += Buffer.byteLength(endData);//结束符占报文长度

		let req = options['http'].request({
			host: options['host'],
			port: options['port'],
			path: options['path'],
			query: options['query'],
			method: 'POST'
		}, function (res) {
			let ret = '';
			res.on('data', function(body) {
				ret += body;
			});

			res.on('end', function() {
				if(ret) {
					try {
						ok(JSON.parse(ret));
					} catch (e){
						fail({message: 'JSON Parese Error', code: e.code,data:ret});
					}
				}
				else{
					ok(ret);
				}
			});

			res.on('error', function (e) {
				fail({error:e,data:{fileData:startData,paramData:paramsData}});
				//fail({code: 509, message: radon.util.LANG('远程计算机拒绝连接')});
			})
		});

		req.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundaryKey + '');
		req.setHeader('Content-Length', contentLength);
		req.setHeader('Accept', 'application/json');

		//写入表单数据到http body
		if(paramsData && paramsData.length > 0){
			req.write(paramsData);
		}

		//写入文件到http body
		var index = 0;
		var doReadStream = function () {
			if(fileIndex){
				req.write(startData[index]);
				let readStream = fs.createReadStream(fileUrl[index], {buffer: 100 * 1024});
				readStream.pipe(req, {end: false});
				readStream.on('end', function (data) {
					index++;
					if (index >= fileIndex) {
						req.write(endData);
						req.end();
						return null;
					} else {
						req.write("\r\n");
						doReadStream();
					}

				});
			}
		};


		if (fileIndex > 0) {
			if(paramsData && paramsData.length > 0){
				req.write("\r\n");
			}
			//有文件写入
			doReadStream();
		} else {
			req.write(endData);
			req.end();
		}

		req.on('error', function(err) {
			//fail({error:err,data:{fileData:startData,paramData:paramsData}});
			fail({code: 509, message: radon.util.LANG('远程计算机拒绝连接')});
		});

	});
}
exports.http_post_form = http_post_form;

